using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Reflection;
using System.Windows;

namespace Wp7Tools.Commands {
	public class EventsCollection : DependencyObjectCollection<Map> {
		private DependencyObject _dependencyObject;

		public EventsCollection() {
			//if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) return;
		}

		public void SetDependencyObject(DependencyObject dependencyObject) {
			_dependencyObject = dependencyObject;
			RegisterEventToMaps(this);
			CollectionChanged += OnCollectionChanged;
		}

		private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
			RegisterEventToMaps(e.NewItems);
			UnregisterEventFromMaps(e.OldItems);
		}

		private void RegisterEventToMaps(IList maps) {
			if (maps == null) return;
			foreach (Map map in maps) {
				Map localMap = map;
				RegisterEvent(_dependencyObject, map.Event, (s, a) => EventFired(localMap, a));
			}
		}

		public void RegisterEvent(object obj, string eventName, Action<object, object> eventHandler) {
			EventInfo eventInfo = GetEventInfo(obj, eventName);
			Delegate del = Delegate.CreateDelegate(eventInfo.EventHandlerType, eventHandler.Target, eventHandler.Method);
			eventInfo.AddEventHandler(obj, del);
		}

		public void UnregisterEvent(object obj, string eventName, Action<object, object> eventHandler) {
			EventInfo eventInfo = GetEventInfo(obj, eventName);
			Delegate del = Delegate.CreateDelegate(eventInfo.EventHandlerType, eventHandler.Target, eventHandler.Method);
			eventInfo.RemoveEventHandler(obj, del);
		}

		private EventInfo GetEventInfo(object obj, string eventName) {
			Type type = obj.GetType();
			return type.GetEvent(eventName);
		}

		private void UnregisterEventFromMaps(IList maps) {
			if (maps == null) return;
			foreach (Map map in maps) {
				Map localMap = map;
				UnregisterEvent(_dependencyObject, map.Event, (s, a) => EventFired(localMap, a));
			}
		}

		public void EventFired(Map map, object eventArgs) {
            //Without this line we can have exceptions in design mode
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) return;
			object dataContext = GetDataContext(map, _dependencyObject);
			string methodName = map.ToMethod;
			CallMethod(map, dataContext, methodName, eventArgs);
		}

		private static object GetDataContext(Map map, DependencyObject dependencyObject) {
            if(map.OfDataContext == null && !(dependencyObject is FrameworkElement)) {
                throw new NotSupportedException("When not a framework element, you must set the parameter OfDataContext. Error trying to map event [" + map.Event + "] to method [" + map.ToMethod + "]. Object: " + dependencyObject);
            }
			return map.OfDataContext ?? dependencyObject.GetValue(FrameworkElement.DataContextProperty);
		}

		private static void CallMethod(Map map, object dataContext, string methodName, object eventArgs) {
			List<object> parameters = new List<object>();
			if (map.SendingEventArgs) {
				parameters.Add(eventArgs);
			}
			if (map.HasParam) {
				parameters.Add(map.WithParam);
			}
			try {
				Reflector.CallMethod(dataContext, methodName, parameters.ToArray());				
			}
			catch(ReflectorException ex) {
				Debug.WriteLine("WARN: " + ex.Message);
			}
		}
	}
}